package http

import (
	"bytes"
	"strconv"
)

const (
	SPACE     = " "
	CRTL      = "\r\n"
	COLON     = ":"
	SEMICOLON = ";"
)

type StartLine struct {
	Method  string
	URL     string
	Version string
}

type Body struct {
	data bytes.Buffer
}

type Header struct {
	header map[string][]string
}

type Request struct {
	StartLine StartLine
	Header    Header
	Body      Body
}

func (this *StartLine) Encode() []byte {
	startLine := ""
	startLine += this.Method
	startLine += SPACE
	startLine += this.URL
	startLine += SPACE
	startLine += this.Version
	startLine += CRTL
	return []byte(startLine)
}

func (this *Request) GetContentLength() int {
	return this.Header.GetContentLength()
}

func (this *Request) GetMethod() string {
	return this.StartLine.Method
}

func (this *Header) Set(key string, value string) {
	this.header[key] = []string{value}
}

func (this *Header) Add(key string, value string) {
	this.header[key] = append(this.header[key], value)
}

func (this *Header) GetContentLength() int {
	contentLengthValue := this.header[ContentLength]
	if contentLengthValue == nil {
		return -1
	}
	contentLen, err := strconv.Atoi(contentLengthValue[0])
	if err != nil {
		panic("content-length is valid")
	}
	return contentLen
}

func (this *Header) Get(key string) []string {
	return this.header[key]
}

func (this *Header) Encode() []byte {
	headerStr := ""
	for key, value := range this.header {
		headerStr += key
		headerStr += COLON
		headerStr += SPACE

		for i := 0; i < len(value)-1; i++ {
			headerStr += value[i]
			headerStr += SEMICOLON
		}
		headerStr += value[len(value)-1]
		headerStr += CRTL
	}
	headerStr += CRTL
	return []byte(headerStr)
}

func (this *Body) Read(p []byte) (n int, err error) {
	writeLen, err := this.data.Write(p)
	return writeLen, err
}

func (this *Body) Write(p []byte) (n int, err error) {
	readLen, err := this.data.Read(p)
	return readLen, err
}

func (this *Request) Encode() []byte {
	requestEncodeBytes := make([]byte, 0)
	requestEncodeBytes = append(requestEncodeBytes, this.StartLine.Encode()...)
	requestEncodeBytes = append(requestEncodeBytes, this.Header.Encode()...)

	if this.GetContentLength() >= 0 {
		requestEncodeBytes = append(requestEncodeBytes, this.encodeBody()...)
	}

	return requestEncodeBytes
}

func (this *Request) encodeBody() []byte {
	bodyLen := this.Header.GetContentLength()

	bodyData := make([]byte, bodyLen)
	this.Body.Read(bodyData)
	return bodyData
}

func CreateRequest() *Request {
	req := &Request{
		Header: Header{
			header: map[string][]string{},
		},
	}

	return req
}
